import csv
from util import CSVHeaders, CSVHeaderError
import threading
import types
from pspy import Notification

class Runner(object):
    """
    """
    def __init__(self, fname):
        "Constructor"
        self.fname = fname
        self._fileHdrs = None
        self._rowCount = 0
        
    #-----------------------------------
    # setup a file headers property
    def _setFileHeaders(self, hdrs=None):
        self._fileHdrs = hdrs

    def _getFileHeaders(self, default=None):
        return self._fileHdrs or default
    
    fileHeaders = property(_getFileHeaders, _setFileHeaders, None, "Column headers from first line of csv file")

    #----------------------------------------
    # read only property for rows processed
    def _getRowCount(self):
        return self._rowCount
    
    rowsProcessed = property(_getRowCount)
    
    ##
    # Go button
    def run(self):
        """
        Main processor API that provides the scaffolding
        for quickly building Runners.  This method is
        responsible for co-ordinating the processing of
        the file.  It hooks in the pre- and post-processors
        as well as the the main readling loop and the 
        row processor.
        """
        # first things first
        self._preProcess()
        
        # create reader
        f = self.fname
        
        if type(f) == types.StringType:
            f = open(f)
        
        # just in case....
        try:
            rdr = csv.reader(f)
            self._read(rdr)
        
        except Exception,ex:
            n = Notification("Error running....")
            n.setException(ex)
        
        finally:
            # free up reader     
            try:
                f.close()
            except:
                pass
        
        # post process if needed
        self._postProcess()
        
    def _read(self, rdr):
        """
        Simple mapping to the files own    colum headers 
        (which must be in the first non-empty line 
        of the file).
        """
        # start reader loop
        
        for l in rdr:
            if not self._fileHdrs:
                # save off the first line
                # as the column headers
                self._fileHdrs = l
            else:
                # process the remainng rows
                self._processRow(dict(zip(self._fileHdrs, l)))

                # and count them
                self._rowCount += 1
                

    #------------------------------------------------------------
    # Override targets for ancestor runners            
    # target runner processor fired from
    # the reader loop
    def _processRow(self, row):
        "Override me..."
        print row

    def _postProcess(self):
        "Override me..."
        pass

    def _preProcess(self):
        "Override me..."
        pass
    
class SmartRunner(Runner):
    
    def __init__(self, fname, headers=None):
        "Constructor"
        Runner.__init__(self, fname)
        
        # set up the 'smart headers'
        self._headers = headers

    #-------------------------------------------------
    #  Public property
    #
    def setCSVHeaders(self, headers):
        if not headers or headers.__class__.__name__ == 'CSVHeaders':
            # preparsed
            self._headers = headers
        else:
            # parse header string now
            self._headers = CSVHeaders(headers)
            
    def getCSVHeaders(self):
        return self._headers
    # create the csvHeaders property            
    csvHeaders = property(getCSVHeaders, setCSVHeaders)

    def _read(self, rdr):
        """
        Override to provide the 'smart header' matching.
        """
        if not self._headers:
            raise CSVHeaderError("No CSVHeaders")

        dMap = None

        # check for presets....
        if self.fileHeaders:
            dMap = self.csvHeaders.validate(self.fileHeaders)
            
        # start reader loop
        for l in rdr:
            # first line may be titles line
            if not dMap:
                dMap = self._headers.validate(l)
            else:
                # process the row
                self._processRow(self._headers.parseData(l, map=dMap))

class AsyncRunner(threading.Thread):
    """
    A thread that wraps a runner.  Use start()
    to go, it's a really a thread not a runner.  
    We'll work in this in the future...
    """    
    def __init__(self, runner=None):
        threading.Thread.__init__(self)
        self.setRunner(runner);
        
    def setRunner(self, runner):
        """
        Method to explicitly set the wrapped runner.
        Handy for factory functions. 
        """
        self._runner = runner
    
    #---------------------------------
    # wrapper API for pass through to
    # inner runner
    def _preProcess(self):
        self._runner.preProcess()
        
    def _postProcess(self):
        self._runner.postProcess()
        
    def _processRow(self, row):
        self._runner.procRow(row)
        
    def run(self):
        self._runner.run()
        
class ProxyRunner(Runner):
    """
    Open adaptor to allow an external class 
    to supply it's own processors (works nicely 
    when you need to process more than ore CSV 
    file).  Supply your own call backs for 
    pre, post and row processor methods either
    in the constructor or through the properties. 
    """
    def __init__(self,fname, runProc=None, preProc=None,postProc=None):
        """
        Initialize the Runner framework
        and wire in the proxy processors
        if supplied.
        """
        Runner.__init__(self, fname) 
        self._runProc = runProc
        self._preProc = preProc
        self._postProc = postProc

    #------------------------------
    # proxy processes as properties
    def _setRowProc(self,proc):
        self._rowProc = proc
    def _getRowProc(self):
        return self._rowProc
    rowProcessor = property(_getRowProc,_setRowProc)
    
    def _setPreProc(self,proc):
        self._preProc = proc
    def _getPreProc(self):
        return self._preProc
    preProcessor = property(_getPreProc,_setPreProc)

    def _setPostProc(self,proc):
        self._postProc = proc
    def _getPostProc(self):
        return self._postProc
    postProcessor = property(_getPostProc,_setPostProc)

    #---------------------------------
    # wrapper API for pass through to
    # inner runner
    def _processRow(self,data):
        if self._runProc:
            self._runProc(data)
            
    def _preProcess(self):
        if self._preProc:
            self._preProc()
        
    def _postProcess(self):
        if self._postProc:
            self._postProc()
        
        
